package mint.modules.generator.expression;

import java.util.ArrayList;
import java.util.Formatter;
import java.util.List;

import org.antlr.runtime.tree.Tree;

import mint.modules.generator.Scope;
import mint.modules.type.Type;

public class ExprNode extends Expression {
	private Expression expr;
	private String assigmentOperator;
	private Expression rightExpr;
	private Expression auxExpr;
	private int line;
	private List<String> errors;
	private Type type;

	public ExprNode(Scope theParent, ExpressionType exprType, Tree exprTree) {
		super(theParent, exprType);		
		
		auxExpr = null;
		type = null;
		
		if (exprTree.getChildCount() > 1)
		{	
			if (exprType == ExpressionType.RightLeft)
				expr = new ConditionalExprNode(theParent, ExpressionType.RightLeft, exprTree.getChild(0));
			else
				expr = new ConditionalExprNode(theParent, ExpressionType.Left, exprTree.getChild(0));
			auxExpr = new ConditionalExprNode(theParent, ExpressionType.Right, exprTree.getChild(0));
			assigmentOperator = exprTree.getChild(1).getChild(0).getText();
			rightExpr = new ExprNode(theParent, ExpressionType.Right, exprTree.getChild(1).getChild(1));
		}
		else
		{
			expr = new ConditionalExprNode(theParent, ExpressionType.Right, exprTree.getChild(0));
			assigmentOperator = null;
			rightExpr = null;
		}
		line = exprTree.getLine();
		errors = new ArrayList<String>();
	}

	@Override
	public void testSemantic() {
		expr.testSemantic();
		type = expr.getType();
		
		if(assigmentOperator != null)
		{
			auxExpr.testSemantic();
			rightExpr.testSemantic();
			
			if (expr.getExpressionType() == ExpressionType.Left)
				type = Type.voidType;
			
			if (expr.getType().equals(Type.floatType))
			{
				if (!(rightExpr.getType().equals(Type.intType) || rightExpr.getType().equals(Type.floatType)))
					errors.add(line + ": incompatible types: " + expr.getType().getCompleteName() + " and " + rightExpr.getType().getCompleteName());
			}
			else
				if (!rightExpr.getType().equals(expr.getType()))
				{
					if(!rightExpr.getType().equals(Type.objectNullType))
						errors.add(line + ": incompatible types: " + expr.getType().getCompleteName() + " and " + rightExpr.getType().getCompleteName());
				}
		}

	}

	@Override
	public List<String> getSemanticErrors() {
		errors.addAll(expr.getSemanticErrors());
		if (rightExpr != null)
			errors.addAll(rightExpr.getSemanticErrors());
		return errors;
	}

	@Override
	public void emit(Formatter output) {
		if (assigmentOperator != null)
		{
			if (assigmentOperator.equals("="))
			{
				expr.emit(output);
				rightExpr.emit(output);
				if (expr.getType().equals(Type.floatType) && rightExpr.getType().equals(Type.intType))
					output.format("i2f\n");
				expr.leftEmit(output);
			} else if (assigmentOperator.equals("*="))
			{
				expr.emit(output);
				auxExpr.emit(output);
				rightExpr.emit(output);
				if (expr.getType().equals(Type.floatType) && rightExpr.getType().equals(Type.intType))
					output.format("i2f\n");
				if (expr.getType().equals(Type.floatType))
					output.format("fmul\n");
				else if (expr.getType().equals(Type.intType))
					output.format("imul\n");				
				expr.leftEmit(output);
			} else if (assigmentOperator.equals("/="))
			{
				expr.emit(output);
				auxExpr.emit(output);
				rightExpr.emit(output);
				if (expr.getType().equals(Type.floatType) && rightExpr.getType().equals(Type.intType))
					output.format("i2f\n");
				if (expr.getType().equals(Type.floatType))
					output.format("fdiv\n");
				else if (expr.getType().equals(Type.intType))
					output.format("idiv\n");	
				expr.leftEmit(output);
			} else if (assigmentOperator.equals("%="))
			{
				expr.emit(output);
				auxExpr.emit(output);
				rightExpr.emit(output);
				output.format("irem\n");				
				expr.leftEmit(output);
			} else if (assigmentOperator.equals("-="))
			{
				expr.emit(output);
				auxExpr.emit(output);
				rightExpr.emit(output);
				if (expr.getType().equals(Type.floatType) && rightExpr.getType().equals(Type.intType))
					output.format("i2f\n");
				if (expr.getType().equals(Type.floatType))
					output.format("fsub\n");
				else if (expr.getType().equals(Type.intType))
					output.format("isub\n");			
				expr.leftEmit(output);
			} else if (assigmentOperator.equals("+="))
			{
				expr.emit(output);
				if (expr.getType().equals(Type.stringType))
				{
					output.format("new java/lang/StringBuffer\n");
					output.format("dup\n");
					output.format("invokespecial java/lang/StringBuffer/<init>()V\n");					
					auxExpr.emit(output);
					output.format("invokevirtual java/lang/StringBuffer/append(Ljava/lang/String;)Ljava/lang/StringBuffer;\n");					
					rightExpr.emit(output);
					output.format("invokevirtual java/lang/StringBuffer/append(Ljava/lang/String;)Ljava/lang/StringBuffer;\n");
					output.format("invokevirtual java/lang/StringBuffer/toString()Ljava/lang/String;\n");
				}
				else
				{					
					auxExpr.emit(output);
					rightExpr.emit(output);
					if (expr.getType().equals(Type.floatType) && rightExpr.getType().equals(Type.intType))
						output.format("i2f\n");
					if (expr.getType().equals(Type.floatType))
						output.format("fadd\n");
					else if (expr.getType().equals(Type.intType))
						output.format("iadd\n");
				}
				expr.leftEmit(output);
			} else if (assigmentOperator.equals("&="))
			{
				expr.emit(output);
				auxExpr.emit(output);
				rightExpr.emit(output);
				output.format("iand\n");				
				expr.leftEmit(output);
			} else if (assigmentOperator.equals("|="))
			{
				expr.emit(output);
				auxExpr.emit(output);
				rightExpr.emit(output);
				output.format("ior\n");				
				expr.leftEmit(output);
			} else if (assigmentOperator.equals("^="))
			{
				expr.emit(output);
				auxExpr.emit(output);
				rightExpr.emit(output);
				output.format("ixor\n");				
				expr.leftEmit(output);
			} 
		} else
			expr.emit(output);
	}

	@Override
	public int getLine() {
		return line;
	}

	@Override
	public Type getType() {
		return type;
	}

	@Override
	public void leftEmit(Formatter output) {
		expr.leftEmit(output);		
	}

}
